﻿/*
 * This file is part of MonoSettlers.
 *
 * Copyright (C) 2010-2011 Christoph Husse
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors: 
 *      # Christoph Husse
 * 
 * Also checkout our homepage: http://opensettlers.codeplex.com/
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using OpenTK;
using OpenTK.Input;
using OpenTK.Graphics;
using System.Runtime.InteropServices;
using System.Threading;

#if EMBEDDED
    using OpenTK.Graphics.ES20;
#else
    using OpenTK.Graphics.OpenGL;
#endif

namespace MonoSettlers.RenderSystem
{

    internal class SelectionModel : ISelectionModel
    {
        public List<IMovable> MovableList { get; private set; }

        public IRenderSystem Parent { get; private set; }
        public IEnumerable<IMovable> Movables { get { return MovableList; } }

        public event DOnSelectionChanged OnChange;

        public SelectionModel(IRenderSystem inParent)
        {
            Parent = inParent;
            MovableList = new List<IMovable>();
        }

        public void RaiseChange()
        {
            if (OnChange != null)
                OnChange();
        }
    }

    /// <summary>
    /// This class and the whole GLRenderer-Assembly is expected to change heavily in near future, 
    /// this is why I skip many documentation at this point.
    /// </summary>
    public partial class GLRenderer : IRenderSystem
    {
        private System.Diagnostics.Stopwatch m_Watch = new System.Diagnostics.Stopwatch();
        private readonly List<int> m_MouseState = new List<int>();
        private readonly List<int> m_KeyboardState = new List<int>();
        private LinkedList<RenderableVisual> m_Visuals = new LinkedList<RenderableVisual>();
        private const Double VisualsPerScreenLine = 30;
        private readonly LinkedList<NativeTexture> m_Textures = new LinkedList<NativeTexture>();
        private System.Windows.Forms.Timer m_CtrlTimer = null;
        private NativeTexture[] m_BuildingGridTextures;
        private int[,] m_BuildingGrid = new int[(int)VisualsPerScreenLine, (int)VisualsPerScreenLine];
        private Point m_ScreenXY = new Point(0, 0);
        private GLProgram m_2DSceneProgram, m_2DSceneSelectProgram;
        private Matrix4 m_ProjMatrix, m_ViewMatrix, m_ModelMatrix;
        private readonly List<IRenderableVisual> m_SelectionPassResults = new List<IRenderableVisual>();

        internal const Int32 SelectionGranularity = 5;
        internal Int32 CurrentTextureID { get; set; }

        public ISelectionModel Selection { get; private set; }

        public IRenderableVisual MouseOverVisual { get; private set; }
        public bool IsSelectionPass { get; private set; }
        public int MapSize { get; private set; }
        public int ViewportWidth { get; private set; }
        public int ViewportHeight { get; private set; }
        public bool IsBuildingGridVisible { get; private set; }
        internal bool IsRenderPass { get; private set; }
        public Double Scale { get; private set; }
        public bool IsTerminated { get; private set; }
        public Point MouseXY { get; private set; }
        public Point ScreenXY { get { return m_ScreenXY; }
            set
            {
                Point newPos = new Point(
                    Math.Max(0, Math.Min(MapSize - 1, value.X)),
                    Math.Max(0, Math.Min(MapSize - 1, value.Y)));

                if (newPos == m_ScreenXY)
                    return;

                m_ScreenXY = newPos;

                if (OnScreenMove != null)
                    OnScreenMove(this);
            }
        }
        public IEnumerable<int> MouseState { get { lock (m_MouseState) { return m_MouseState.ToArray(); } } }
        public IEnumerable<int> KeyboardState { get { lock (m_KeyboardState) { return m_KeyboardState.ToArray(); } } }
        public Graphics UISurface { get { return null; } }

        public event DNotifyHandler<IRenderSystem, IRenderableVisual> OnMouseEnter;
        public event DNotifyHandler<IRenderSystem, IRenderableVisual> OnMouseLeave;
        public event DNotifyHandler<IRenderSystem, int> OnMouseDown;
        public event DNotifyHandler<IRenderSystem, int> OnMouseUp;
        public event DNotifyHandler<IRenderSystem> OnMouseDrag;
        /// <summary>
        /// Make no assumptions about thread context!
        /// </summary>
        public event DNotifyHandler<IRenderSystem> OnScreenMove;

        public event DNotifyHandler<IRenderSystem, int> OnKeyDown;
        public event DNotifyHandler<IRenderSystem, int> OnKeyUp;
        public event DNotifyHandler<IRenderSystem> OnKeyRepeat;

        

        public void ShowBuildingGrid(Func<Point, int> inLevelFunc)
        {
            for (int x = 0; x < VisualsPerScreenLine; x++)
            {
                for (int y = 0; y < VisualsPerScreenLine; y++)
                {
                    Point origPos = new Point(ScreenXY.X + x, ScreenXY.Y + y);
                    Point clampPos = new Point(Math.Min(MapSize - 1, origPos.X), Math.Min(MapSize - 1, origPos.Y));

                    if (origPos != clampPos)
                    {
                        // we are out of range (hide level indicators here)
                        m_BuildingGrid[x, y] = -1;
                    }
                    else
                    {
                        // gather level info
                        m_BuildingGrid[x, y] = inLevelFunc(clampPos);
                    }
                    
                }
            }

            IsBuildingGridVisible = true;
        }

        public void HideBuildingGrid()
        {
            IsBuildingGridVisible = false;
        }



        public static void CheckError()
        {
            CheckError("OpenGL call failed unexpectedly.");
        }

        public static void CheckError(String inMessage, params Object[] inArgs)
        {
            ErrorCode errCode = GL.GetError();

            switch (errCode)
            {
                case ErrorCode.NoError:
                    return;

                default:
                    {
                        throw new GLException(errCode, inMessage, inArgs);
                    }
            }
        }

        public Bitmap LoadAmbientSnapshot(String inAnimClass)
        {
            var animClass = AnimationLibrary.Instance.FindClass(inAnimClass);

            if (animClass.AmbientSet != null)
            {
                return animClass.AmbientSet.Animations.First().Frames.First().Source;
            }
            else
            {
                return animClass.Sets.First().Animations.First().Frames.First().Source;
            }
        }
    }
}
